﻿using System;
using System.Threading;

namespace Gonte.Commands
{
    /// <summary>
    /// Executes an asynchronous command
    /// </summary>
    /// <typeparam name="R">The result type</typeparam>
    /// <typeparam name="P">The parameter type</typeparam>
    public class AsynchronousCommand<R, P>
        : ICommand
    {
        #region Constructors

        public AsynchronousCommand(
            P parameter,
            Invoker invoker,
            SuccessHandler onSuccess,
            ExceptionHandler onException,
            CleanUpHandler onCleanUp)
        {
            parameter_ = parameter;
            invoker_ = invoker;
            onSuccess_ = onSuccess;
            onException_ = onException;
            onCleanUp_ = onCleanUp;
        }

        #endregion

        #region ICommand Members

        public void Execute()
        {
            asyncResult_ = invoker_.BeginInvoke(parameter_, Callback, null);
            Thread.Sleep(10); // Allow to call the callback on very fast processes
        }

        #endregion

        public IAsyncResult AsyncResult
        {
            get { return asyncResult_; }
        }

        #region Overridables

        protected virtual void OnSuccess(R result)
        {
            if (onSuccess_ != null)
            {
                onSuccess_(result);
            }
        }

        protected virtual void OnException(Exception exception)
        {
            if (onException_ != null)
            {
                onException_(exception);
            }
        }

        protected virtual void OnCleanUp()
        {
            if (onCleanUp_ != null)
            {
                onCleanUp_(parameter_);
            }
        }

        #endregion

        #region Helpers

        private void Callback(IAsyncResult asynchResult)
        {
            try
            {
                OnSuccess(invoker_.EndInvoke(asynchResult));
            }
            catch (Exception exception)
            {
                OnException(exception);
            }
            finally
            {
                OnCleanUp();
            }
        }

        #endregion

        #region Delegates

        public delegate R Invoker(P parameter);
        public delegate void SuccessHandler(R result);
        public delegate void ExceptionHandler(Exception exception);
        public delegate void CleanUpHandler(P parameter);

        #endregion

        #region Fields

        protected P parameter_;
        private Invoker invoker_;
        private IAsyncResult asyncResult_;
        protected SuccessHandler onSuccess_;
        protected ExceptionHandler onException_;
        protected CleanUpHandler onCleanUp_;

        #endregion
    }
}
